;define(function (require) {
    'use strict';
    var d3=require('d3');
    var $=require('jquery');

    var π = Math.PI,
        τ = 2 * π;
    /*!
     * @license Open source under BSD 2-clause (http://choosealicense.com/licenses/bsd-2-clause/)
     * Copyright (c) 2015, Curtis Bratton
     * All rights reserved.
     *
     * Liquid Fill Gauge v1.1
     */
    var defconfig={
            borderColor:'#D6DFEF',
            innerBorderColor:'#EBF4FA',
            arrowColor:'#427EC9',
            startColor:'#6A9AD7',
            endColor:'#A1D2F0',
            minValue: 0, // The gauge minimum value.
            maxValue: 100, // The gauge maximum value.
            circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
            circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
            circleColor: "#178BCA", // The color of the outer circle.
            waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
            waveCount: 1, // The number of full waves per width of the wave circle.
            waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
            waveAnimateTime: 1000, // The amount of time in milliseconds for a full wave to enter the wave circle.
            waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
            waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
            waveAnimate: true, // Controls if the wave scrolls or is static.
            waveColor: "#178BCA", // The color of the fill wave.
            waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
            textVertPosition: .8, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
            textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
            valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
            displayPercent: true, // If true, a % symbol is displayed after the value.
            textColor: "#999999", // The color of the value text when the wave does not overlap it.
            waveTextColor: "#999999", // The color of the value text when the wave overlaps it.
            img:null,
            text:'图例名称',
            majorTicks:5,
            minorTicks:2,
            transitionDuration:500
        };

    function Gauge(elementId, value, opts) {
        $(elementId).html('');
        var config=defconfig;
        if(opts != null)  $.extend(true, config, opts);
        var width=$(elementId).width()-30,
            height=$(elementId).height();
        var gauge = d3.select(elementId).append('svg')
            .attr("width", width)       //设定宽度
            .attr("height", height);
        var radius = Math.min(parseInt(width), parseInt(height))/2;
        var locationX = parseInt(width)/2 - radius;
        var locationY = parseInt(height)/2 - radius;
        var fillPercent =0.25;//= Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;


        config.cx = width / 2;
        config.cy = width/2+(height-width)/2;
        var cx=config.cx,cy=config.cy;
        config.range=config.maxValue-config.minValue;

        var waveHeightScale;
        if(config.waveHeightScaling){
            waveHeightScale = d3.scale.linear()
                .range([0,config.waveHeight,0])
                .domain([0,50,100]);
        } else {
            waveHeightScale = d3.scale.linear()
                .range([config.waveHeight,config.waveHeight])
                .domain([0,100]);
        }

        // Scales for drawing the outer circle.
        var gaugeCircleX = d3.scale.linear().range([0,2*Math.PI]).domain([0,1]);
        var gaugeCircleY = d3.scale.linear().range([0,radius]).domain([0,radius]);

        var textFinalValue = parseFloat(value).toFixed(2);
        var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
        var percentText = config.displayPercent?"%":"";

        // Center the gauge within the parent SVG.
        var gaugeGroup = gauge.append("g")
            .attr('transform','translate('+locationX+','+locationY+')');

        // Draw the border.
        var outerRadius = gaugeCircleY(radius)-1,
            innerRadius = outerRadius - 1;
        var border=d3.svg.arc()
            .startAngle(gaugeCircleX(0))
            .endAngle(gaugeCircleX(1))
            .outerRadius(outerRadius)
            .innerRadius(innerRadius);
        gaugeGroup.append("path")
            .attr("d", border)
            .style("fill", config.borderColor)
            .attr('transform','translate('+radius+','+radius+')');
        // Draw the border2.
        outerRadius=outerRadius-outerRadius*0.08;
        innerRadius=outerRadius-outerRadius*0.2;
        var border2 = d3.svg.arc()
            .startAngle(gaugeCircleX(0))
            .endAngle(gaugeCircleX(1))
            .outerRadius(outerRadius)
            .innerRadius(innerRadius);
        gaugeGroup.append("path")
            .attr("d", border2)
            .style("fill", config.innerBorderColor)
            .attr('transform','translate('+radius+','+radius+')');

        // Draw the outer circle.
        var defs = gauge.append("defs");
        var linearGradient = defs.append("linearGradient")
                        .attr("id","linearColor_"+elementId)
                        .attr("x1","0%")
                        .attr("y1","100%")
                        .attr("x2","100%")
                        .attr("y2","0%");
        var stop1 = linearGradient.append("stop")
                        .attr("offset","0%")
                        .style("stop-color",config.startColor);
        var stop2 = linearGradient.append("stop")
                        .attr("offset","100%")
                        .style("stop-color",config.endColor);
        outerRadius=outerRadius-(outerRadius-innerRadius);
        innerRadius=outerRadius-outerRadius*0.2;
        var border3 = d3.svg.arc()
            .startAngle(gaugeCircleX(0))
            .endAngle(gaugeCircleX(1))
            .outerRadius(outerRadius)
            .innerRadius(innerRadius);
        gaugeGroup.append("path")
            .attr('class','innerborder')
            .attr("d", border3)
            .style("fill", "url('#linearColor_"+elementId+"')")
            .attr('transform','translate('+radius+','+radius+')');

        var textPixels = (config.textSize*innerRadius/2);
        var circleThickness = config.circleThickness * innerRadius;
        var circleFillGap = config.circleFillGap * innerRadius;
        var fillCircleMargin = radius-innerRadius;
        var fillCircleRadius = innerRadius-2;
        var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);

        var waveLength = fillCircleRadius*2/config.waveCount;
        var waveClipCount = 1+config.waveCount;
        var waveClipWidth = waveLength*waveClipCount;

        // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
        var textRounder = function(value){ return Math.round(value); };
        if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
            textRounder = function(value){ return parseFloat(value).toFixed(1); };
        }
        if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
            textRounder = function(value){ return parseFloat(value).toFixed(2); };
        }

        // Data for building the clip wave area.
        var data = [];
        for(var i = 0; i <= 50*waveClipCount; i++){
            data.push({x: i/(40*waveClipCount), y: (i/(40))});
        }

        
        // Scales for controlling the size of the clipping path.
        var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
        var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);

        // Scales for controlling the position of the clipping path.
        var waveRiseScale = d3.scale.linear()
            // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
            // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
            // circle at 100%.
            .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
            .domain([0,1]);
        var waveAnimateScale = d3.scale.linear()
            .range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
            .domain([0,1]);

        // Scale for controlling the position of the text within the gauge.
        var textRiseScaleY = d3.scale.linear()
            .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
            .domain([0,1]);

        // Text where the wave does not overlap.
        var text1 = gaugeGroup.append("text")
            .text(textRounder(textStartValue) + percentText)
            .attr("class", "liquidFillGaugeText")
            .attr("text-anchor", "middle")
            .attr("font-size", textPixels + "px")
            .style("fill", config.textColor)
            .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

        // The clipping wave area.
        var clipArea = d3.svg.area()
            .x(function(d) { return waveScaleX(d.x); } )
            .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
            .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
        var waveGroup = gaugeGroup.append("defs")
            .append("clipPath")
            .attr("id", "clipWave" + elementId);
        var wave = waveGroup.append("path")
            .datum(data)
            .attr("d", clipArea)
            .attr("T", 0);

        var defs = gauge.append("defs");
        var waverGradient = defs.append("linearGradient")
                        .attr("id","waverGradient_"+elementId)
                        .attr("x1","0%")
                        .attr("y1","50%")
                        .attr("x2","0%")
                        .attr("y2","100%");
        waverGradient.append("stop")
                        .attr("offset","0%")
                        .style("stop-color",config.waveColor)
                        .style("stop-opacity", 1);
        waverGradient.append("stop")
                        .attr("offset","100%")
                        .style("stop-color",config.waveColor)
                        .style("stop-opacity", 0);
        // The inner circle with the clipping wave attached.
        var fillCircleGroup = gaugeGroup.append("g")
            .attr("clip-path", "url(#clipWave" + elementId + ")");
        fillCircleGroup.append("circle")
            .attr("cx", radius)
            .attr("cy", radius)
            .attr("r", fillCircleRadius)
            .style("fill", "url('#waverGradient_"+elementId+"')");
            //config.waveColor

        // Text where the wave does overlap.
        var text2 = fillCircleGroup.append("text")
            .text(textRounder(textStartValue) + percentText)
            .attr("class", "liquidFillGaugeText")
            .attr("text-anchor", "middle")
            .attr("font-size", textPixels + "px")
            .style("fill", '#ffffff')
            .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

        // Make the value count up.
        textTween(value);
        function textTween(value){
            var textTween = function(){
                var i = d3.interpolate(this.textContent, parseFloat(value).toFixed(2));
                return function(t) { this.textContent = textRounder(i(t)) + percentText; }
            };
            text1.transition()
                .duration(config.waveRiseTime)
                .tween("text", textTween);
            text2.transition()
                .duration(config.waveRiseTime)
                .tween("text", textTween);
        }

        if(config.img){
            var y=radius*2-radius*2*fillPercent-config.img.height*0.35;
            var img=gauge.append('image')
                .attr('x',radius-config.img.width/2)
                .attr('y',y)
                .attr('width',config.img.width)
                .attr('height',config.img.height)
                .attr("xlink:href",config.img.src);
        }

        var title=gauge.append('text')
                .attr('x',0)
                .attr('y',radius*2+40)
                .attr('width',0)
                .attr('style','display:none')
                .attr('dx',((config.title.length*12-140)/2)+'px')
                .attr('dy',10)
                .attr("font-size",  "12px")
                .text(config.title);

        //绘制节点
        var pointsGroup = gauge.append("g")
            .attr("width", width)
            .attr("height", width);

        var fontSize = Math.round(width / 16);
        var majorDelta = config.range / (config.majorTicks - 1);
        for (var major = config.minValue; major <= config.maxValue; major += majorDelta)
        {
            var minorDelta = majorDelta / config.minorTicks;
            for (var minor = major + minorDelta; minor < Math.min(major + majorDelta, config.maxValue); minor += minorDelta)
            {
                var point1 = valueToPoint(minor, 0.75);
                var point2 = valueToPoint(minor, 0.9);
                pointsGroup.append("line")
                            .attr("x1", point1.x)
                            .attr("y1", point1.y)
                            .attr("x2", point2.x)
                            .attr("y2", point2.y)
                            .style("stroke", config.arrowColor)
                            .style("stroke-width", "2px");
            }
            var point1 = valueToPoint(major, 0.75);
            var point2 = valueToPoint(major, 0.9);
            pointsGroup.append("line")
                        .attr("x1", point1.x)
                        .attr("y1", point1.y)
                        .attr("x2", point2.x)
                        .attr("y2", point2.y)
                        .style("stroke", config.arrowColor)
                        .style("stroke-width", "2px");
        }

        function valueToRadians(value){
            var v=valueToDegrees(value) * Math.PI / 180;
            return v;
        }
        function valueToDegrees(value){
            var v=value / config.range * 270 - (config.minValue / config.range * 270 + 45);
            //console.log('valueToDegrees: '+value,v);
            return v;
        }

        function valueToPoint(value, factor){
            //factor=0.75;
            var v={x: config.cx- radius * factor * Math.cos(valueToRadians(value)),
                    y: config.cy- radius * factor * Math.sin(valueToRadians(value))}
            return v;
        }

        var pointerContainer = gauge.append("g")
            .attr("width", width)
            .attr("height", width);
        var midValue = (config.minValue + config.maxValue) / 2;
        var pointerPath = buildPointerPath(midValue);
        var pointerLine = d3.svg.line()
                                    .x(function(d) { return d.x })
                                    .y(function(d) { return d.y })
                                    .interpolate("basis");
        pointerContainer.selectAll("path")
                            .data([pointerPath])
                            .enter()
                                .append("svg:path")
                                    .attr("d", pointerLine)
                                    .style("fill", config.arrowColor)
                                    .style("fill-opacity", 1);

        animatePointer(value);
        function buildPointerPath(value){
            var delta = config.range / 10;
            var head = _valueToPoint(value, 0.9);
            var head1 = _valueToPoint(value - delta*0.2, 0.75);
            var head2 = _valueToPoint(value + delta*0.2, 0.75);

            var tailValue = value - (config.range * (1/(270/360)) / 2);
            var tail = _valueToPoint(tailValue, 0.28);
            var tail1 = _valueToPoint(tailValue - delta, 0.12);
            var tail2 = _valueToPoint(tailValue + delta, 0.12);
            return [head, head1, /**tail2, tail, tail1,**/ head2, head];
            function _valueToPoint(value, factor)
            {
                var point = valueToPoint(value, factor);
                point.x -= config.cx;
                point.y -= config.cy;
                return point;
            }
        }
        // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
        var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
        if(config.waveRise){
            waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
                .transition()
                .duration(config.waveRiseTime)
                .attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
                .each("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
        } else {
            waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
        }

        if(config.waveAnimate) animateWave();
        function animateWave() {
            wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
            wave.transition()
                .duration(config.waveAnimateTime * (1-wave.attr('T')))
                .ease('linear')
                .attr('transform','translate('+waveAnimateScale(1)+',0)')
                .attr('T', 1)
                .each('end', function(){
                    wave.attr('T', 0);
                    animateWave(config.waveAnimateTime);
                });
        }
        /**var sa=0;
        setInterval(function(){
            sa=sa>100?0:sa+10;
            animatePointer(sa);
            textTween(sa);
        },3000);**/
        function animatePointer(value){
            var transitionDuration=!config.transitionDuration ? 500 : config.transitionDuration;
            var pointer = pointerContainer.selectAll("path");
            pointer.transition()
                    .duration(transitionDuration)
                    //.delay(0)
                    //.ease("linear")
                    //.attr("transform", function(d) 
                    .attrTween("transform", function()
                    {
                        var pointerValue = value;
                        if (value > config.maxValue) pointerValue = config.maxValue + 0.02*config.range;
                        else if (value < config.minValue) pointerValue = config.minValue - 0.02*config.range;
                        var targetRotation = (valueToDegrees(pointerValue) -90);
                        var currentRotation = config._currentRotation || targetRotation;
                        config._currentRotation = targetRotation;
                        
                        return function(step){
                            var rotation = currentRotation + (targetRotation-currentRotation)*step;
                            return "translate(" + cx + ", " + cy + ") rotate(" + rotation + ")"; 
                        }
                    });
        }

        function GaugeUpdater(){
            this.update = function(value){
                // var newFinalValue = parseFloat(value).toFixed(2);
                // var textRounderUpdater = function(value){ return Math.round(value); };
                // if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
                //     textRounderUpdater = function(value){ return parseFloat(value).toFixed(1); };
                // }
                // if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
                //     textRounderUpdater = function(value){ return parseFloat(value).toFixed(2); };
                // }

                // var textTween = function(){
                //     var i = d3.interpolate(this.textContent, parseFloat(value).toFixed(2));
                //     return function(t) { this.textContent = textRounderUpdater(i(t)) + percentText; }
                // };
                textTween(value);
                animatePointer(value)

                // text1.transition()
                //     .duration(config.waveRiseTime)
                //     .tween("text", textTween);
                // text2.transition()
                //     .duration(config.waveRiseTime)
                //     .tween("text", textTween);

                var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
                var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
                var waveRiseScale = d3.scale.linear()
                    // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
                    // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
                    // circle at 100%.
                    .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
                    .domain([0,1]);
                var newHeight = waveRiseScale(fillPercent);
                var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
                var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
                var newClipArea;
                if(config.waveHeightScaling){
                    newClipArea = d3.svg.area()
                        .x(function(d) { return waveScaleX(d.x); } )
                        .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
                        .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
                } else {
                    newClipArea = clipArea;
                }

                var newWavePosition = config.waveAnimate?waveAnimateScale(1):0;
                wave.transition()
                    .duration(0)
                    .transition()
                    .duration(config.waveAnimate?(config.waveAnimateTime * (1-wave.attr('T'))):(config.waveRiseTime))
                    .ease('linear')
                    .attr('d', newClipArea)
                    .attr('transform','translate('+newWavePosition+',0)')
                    .attr('T','1')
                    .each("end", function(){
                        if(config.waveAnimate){
                            wave.attr('transform','translate('+waveAnimateScale(0)+',0)');
                            animateWave(config.waveAnimateTime);
                        }
                    });
                waveGroup.transition()
                    .duration(config.waveRiseTime)
                    .attr('transform','translate('+waveGroupXPosition+','+newHeight+')')
            }
        }
        return new GaugeUpdater();
    }
    return Gauge;
});